package spark.network;

import org.apache.log4j.Logger;

import java.net.InetSocketAddress;

import spark.network.protocol.*;
import spark.network.ActiveClientList;
import spark.network.strategy.ClockSyncResStrategy;
import spark.network.strategy.PreLoginResponseAckStrategy;

public abstract class ServerDemonHandler extends spark.network.DemonHandler<ActiveClientList> {
	private static Logger logger = Logger.getLogger(ServerDemonHandler.class);

	private static final int CLOCK_SYNC_REQUESTS_TO_PERFORM = 10;

	private SessionFactory sessionFactory = new SessionFactory();
	private PreLoginResponseAckStrategy preLoginResponseAckStrategy;
	private ClockSyncResStrategy clockSyncResStrategy;

	protected ServerDemonHandler(ActiveClientList clients, ServerSender sender) {
		super(clients, sender);
		ClockSynchronizer clockSynchronizer = new ClockSynchronizer();
		preLoginResponseAckStrategy = new PreLoginResponseAckStrategy(clockSynchronizer, CLOCK_SYNC_REQUESTS_TO_PERFORM);
		clockSyncResStrategy = new ClockSyncResStrategy(clockSynchronizer, CLOCK_SYNC_REQUESTS_TO_PERFORM);
	}

	/**
	 * Handle the prelogin of a client. Once a client is pre logged in he is allowed to communicate with the server.
	 * Before the prelogin all messages he sends except for prelogin itself will be ignored.
	 *
	 * @param preLogin Prelogin packet.
	 * @param source   Address of the client who sent the packet.
	 */
	//TODO : reduce the complexity of this method
	public void handle(PreLogin preLogin, InetSocketAddress source) {
		NetworkConnection clientConnection = connections.getConnection(preLogin.getSession());
		InetSocketAddress responseAddress;
		Response response;
		//a new player is performing the prelogin so we accept him
		if(clientConnection == null) {
			//create an according response
			PreLoginResponse newResponse = new PreLoginResponse(preLogin);
			//give the player a session, an id and put him on our list
			int responsePort = preLogin.getResponsePort();
			if(responsePort == -1) {
				//address to respond to and to receive packets from is the same
				clientConnection = new NetworkConnection(sessionFactory.createSession(), source, source);
			}
			else {
				//there is a separate address to respond to (send packets to)
				clientConnection = new NetworkConnection(sessionFactory.createSession(), source, new InetSocketAddress(source.getAddress(), responsePort));
			}
			responseAddress = clientConnection.getResponseAddress();
			connections.addConnection(clientConnection);
			newResponse.setAccepted(true);
			newResponse.setSession(clientConnection.getSession());
			response = newResponse;
			clientConnection.bufferResponse(response);
		}
		//if the player already has performed a successful pre login
		else {
			BufferedPacket<Response> bufferedResponse = clientConnection.getBufferedResponse(preLogin);
			responseAddress = clientConnection.getResponseAddress();
			//we have never responded to this query before
			if(bufferedResponse == null) {
				PreLoginResponse newResponse = new PreLoginResponse(preLogin);
				newResponse.setAccepted(false);
				newResponse.setMessage("Someone from " + source + " is already connected.");
				response = newResponse;
				clientConnection.bufferResponse(response);
			}
			//player bufferedresponses contains response with same id as request
			else {
				if(bufferedResponse.getPacket().getTimeStampSend() < preLogin.getTimeStampSend()) {
					//we have received a more recent query than the one we have responded to so update our response
					bufferedResponse.getPacket().setTimeStampSend(preLogin.getTimeStampSend());
					response = bufferedResponse.getPacket();
				}
				else {
					//being here we already have responded to a more recent query and got this request out of order
					//so we may ignore the request and just return
					return;
				}
			}
		}
		sender.send(response, responseAddress);
	}

	public void handle(PreLoginResponseAck preLoginResponseAck, InetSocketAddress source) {
		super.handle(preLoginResponseAck, source, preLoginResponseAckStrategy);
	}

	public void handle(ClockSyncRes clockSyncRes, InetSocketAddress source) {
		super.handle(clockSyncRes, source, clockSyncResStrategy);
	}
}
